/**
 * 
 */
package blms.system.manager;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import blms.system.exception.restrictions.RestrictionException;

/**
 * @author thiago
 *
 */
public class MatchesManager {

	private final Map<String, List<Match>> matchesByLeagueID = new HashMap<String, List<Match>>();
	private final Map<Integer, Match> matchByID = new HashMap<Integer, Match>();
	
	private int IDCounter = 1;

	/**
	 * 
	 */
	public void removeAllMatches() {
		
		IDCounter = 0;

		matchByID.clear();
		
		for (String leagueID : matchesByLeagueID.keySet()) {
			matchesByLeagueID.put(leagueID, new LinkedList<Match>());
		}
	}
	
	/**
	 * @param matchID
	 */
	public void deleteMatch(int matchID) {
		
		Match match = getMatchByID(matchID);
		
		removeMatchFromLeague(match);
		matchByID.remove(match.getMatchID());
	}

	/**
	 * @param match
	 */
	private void removeMatchFromLeague(Match match) {
		
		String leagueID = match.getLeagueID();
		List<Match> matches = getLeaguesMatches(leagueID);
		matches.remove(match);
	}
	
	/**
	 * Matches results for a especific league.
	 * 
	 * @param leagueID
	 * @return
	 */
	public int getNumberOfMatches(String leagueID) {
		return getLeaguesMatches(leagueID).size();
	}

	/**
	 * Returns a <code>Collection</code> of <code>Match</code> related to a leagueID.
	 * @param leagueID
	 * @return A <code>Collection</code> of <code>Match</code> related to a leagueID, if
	 * 			there are no matchs related to a league, returns a empty <code>Collection</code>  
	 */
	private List<Match> getLeaguesMatches(String leagueID) {
		
		List<Match> matches = matchesByLeagueID.get(leagueID);
		
		if (matches == null) {
			matches = new LinkedList<Match>();
			matchesByLeagueID.put(leagueID, matches);
		}

		return matches;
	}
	
	/**
	 * Matches results for a especific player.
	 * 
	 * @param playerID
	 * @param leagueID
	 * @return
	 */
	public int getNumberOfMatches(String playerID, String leagueID) {
		return getMatchesFromPlayer(playerID, getLeaguesMatches(leagueID)).size();
	}

	
	/**
	 * @param playerID
	 * @param leagueMatch
	 * @return 
	 */
	private List<Match> getMatchesFromPlayer(String playerID, String leagueID) {
		return getMatchesFromPlayer(playerID, getLeaguesMatches(leagueID));
	}
	
	/**
	 * @param playerID
	 * @param leagueMatch
	 * @return 
	 */
	private List<Match> getMatchesFromPlayer(String playerID, Collection<Match> leagueMatch) {
		
		List<Match> matchesFromPlayer = new LinkedList<Match>();
		
		for (Match match : leagueMatch) {
			if(match.isOnTheMatch(playerID)) {
				matchesFromPlayer.add(match);
			}
		}
		
		return sortByMatchDate(matchesFromPlayer);
	}

	/**
	 * @param matchesFromPlayer
	 * @return
	 */
	private List<Match> sortByMatchDate(List<Match> matchesFromPlayer) {
		
		List<Match> sortedMatches = new LinkedList<Match>();
		sortedMatches.addAll(matchesFromPlayer);
		
		Collections.sort(sortedMatches, new Comparator<Match>() {
			public int compare(Match o1, Match o2) {
				return o1.getMatcheDate().compareTo(o2.getMatcheDate());
			}
		});
		
		return sortedMatches;
	}
	
	/**
	 * @param leagueID
	 * @param startDate
	 * @param endDate
	 * @param index
	 * @return
	 */
	public int getMatchByDate(String leagueID, Date startDate, Date endDate, int index) {
		
		return filterByDates(
								sortByMatchDate(getLeaguesMatches(leagueID)),
								startDate,
								endDate
							)
							.get( index - 1 ).getMatchID();
	}

	/**
	 * @param playerID
	 * @param leagueID
	 * @param startDate
	 * @param endDate
	 * @param index
	 * @return
	 */
	public int getMatchByDate(String playerID, String leagueID, Date startDate, Date endDate, int index) {
		
		return filterByDates(
								sortByMatchDate(getMatchesFromPlayer(playerID, leagueID)),
								startDate,
								endDate
							)
							.get( index - 1 ).getMatchID();
	}
	
	/**
	 * @param matches
	 * @param startDate
	 * @param endDate
	 * @return
	 */
	private List<Match> filterByDates(List<Match> matches, Date startDate, Date endDate) {
		
		List<Match> filteredMatches = new LinkedList<Match>();
		
		for (Match match : matches) {
			
			if( (match.getMatcheDate().compareTo(startDate) >= 0) && (match.getMatcheDate().compareTo(startDate) >= 0)) {
				filteredMatches.add(match);
			}
			
			if( (match.getMatcheDate().compareTo(endDate) > 0)) break;
			
		}
		
		return filteredMatches;
	}

	/**
	 * @param playerID
	 * @param leagueID
	 * @return
	 */
	public int getNumberOfWins(String playerID, String leagueID) {
		
		int winsCount = 0;
		
		for (Match match : getMatchesFromPlayer(playerID, leagueID)) {
			if (match.isTheWinner(playerID)) winsCount++;
		}
		
		return winsCount;
	}
	
	/**
	 * @param playerID
	 * @param leagueID
	 * @return
	 */
	public int getNumberOfLosses(String playerID, String leagueID) {
		
		int lossesCount = 0;
		
		for (Match match : getMatchesFromPlayer(playerID, leagueID)) {
			if (!match.isTheWinner(playerID)) lossesCount++;
		}
		
		return lossesCount;
	}
	
	/**
	 * @param leagueID
	 * @param index
	 * @return
	 */
	public int getMatchID(String leagueID, int index) {
		return getMatch(leagueID, index).getMatchID();
	}

	/**
	 * @param leagueID
	 * @param index
	 * @return
	 */
	private Match getMatch(String leagueID, int index) {
		return getLeaguesMatches(leagueID).get(index - 1);
	}
	
	/**
	 * @param playerID
	 * @param leagueID
	 * @param index
	 * @return
	 */
	public int getMatch(String playerID, String leagueID, int index) {
		return getMatchesFromPlayer(playerID, leagueID).get( index - 1).getMatchID();
	}
	
	/**
	 * @param matchID
	 * @return
	 */
	public Date getMatchDate(int matchID) {
		return getMatchByID(matchID).getMatcheDate();
	}
	
	/**
	 * @param matchID
	 * @return
	 */
	public String getMatchWinner(int matchID) {
		return getMatchByID(matchID).getWinnerID();
	}
	
	/**
	 * @param matchID
	 * @return
	 */
	public String getMatchLoser(int matchID) {
		return getMatchByID(matchID).getLoserID();
	}

	/**
	 * @param matchID
	 * @return
	 */
	private Match getMatchByID(int matchID) {
		return matchByID.get(matchID);
	}
	
	/**
	 * @param leagueId
	 * @param date
	 * @param winnerID
	 * @param loserID
	 * @return
	 * @throws RestrictionException 
	 */
	public int addMatchResult(String leagueId, Date date, String winnerID, String loserID) throws RestrictionException {

		return addMatchResult(leagueId, date, winnerID, loserID, Integer.MIN_VALUE, Integer.MIN_VALUE,
								Integer.MIN_VALUE, Integer.MIN_VALUE);
	}
	
	/**
	 * @param leagueId
	 * @param date
	 * @param winnerID
	 * @param loserID
	 * @param length
	 * @param score
	 * @param longestRunForWinner
	 * @param longestRunForLoser
	 * @return
	 * @throws RestrictionException 
	 */
	public int addMatchResult(String leagueId, Date date, String winnerID, String loserID, int length,
								int score, int longestRunForWinner, int longestRunForLoser) throws RestrictionException {
		
		if(winnerID.equals(loserID)) throw new RestrictionException("Users must be different");
		
		if( (length < 1) && (length != Integer.MIN_VALUE)) throw new RestrictionException("Invalid match length");//colocar este teste dentro de Match
		if( ((score < 0) || (score >= length)) && (score != Integer.MIN_VALUE)) throw new RestrictionException("Invalid score");//colocar este teste dentro de Match
		
		List<Match> matches = getLeaguesMatches(leagueId);
		
		if (matches == null) {
			matches = new LinkedList<Match>();
			matchesByLeagueID.put(leagueId, matches);
		}

		int newMatchID = generateID();
		Match match = new Match(leagueId, date, winnerID, loserID, newMatchID, length, score, 
									longestRunForWinner, longestRunForLoser);
		matchByID.put(newMatchID, match);
		matches.add(match);
		
		return newMatchID;
	}
	
	/**
	 * @param matchID
	 * @param date
	 * @param winnerID
	 * @param loserID
	 * @param length
	 * @param score
	 * @param longestRunForWinner
	 * @param longestRunForLoser
	 */
	public void updateMatchResult(int matchID, Date date, String winnerID, String loserID, int length, int score, int longestRunForWinner, 
										int longestRunForLoser) {
		
		Match match = getMatchByID(matchID);
		match.setMatchDate(date);
		match.setWinnerID(winnerID);
		match.setLoserID(loserID);
		match.setLength(length);
		match.setScore(score);
		match.setLongestRunForWinner(longestRunForWinner);
		match.setLongestRunForLoser(longestRunForLoser);
	}
	
	private String parseIntegerBound(int integerValue) {
		return (isBoundValue(integerValue) ? "" : Integer.toString(integerValue));
	}
	
	private boolean isBoundValue(int integerValue) {
		return ((Integer.MIN_VALUE == integerValue) || (Integer.MAX_VALUE == integerValue));
	}

	/**
	 * @param matchID
	 * @return
	 */
	public String getMatchLength(int matchID) {
		return parseIntegerBound(getMatchByID(matchID).getLength());
	}
	
	/**
	 * @param matchID
	 * @return
	 */
	public String getMatchScore(int matchID) {
		return parseIntegerBound(getMatchByID(matchID).getScore());
	}
	
	/**
	 * @param matchID
	 * @return
	 */
	public String getMatchLongestRunForWinner(int matchID) {
		return parseIntegerBound(getMatchByID(matchID).getLongestRunForWinner());
	}
	
	/**
	 * @param matchID
	 * @return
	 */
	public String getMatchLongestRunForLoser(int matchID) {
		return parseIntegerBound(getMatchByID(matchID).getLongestRunForLoser());
	}
	
	private int generateID() {
		return ++IDCounter;
	}

	private class Match {
		
		private final String leagueID;
		private final int matchID;
		
		private Date matchDate;
		private String winnerID;
		private String loserID;
		private int length;
		private int score;
		private int longestRunForWinner;
		private int longestRunForLoser;

		/**
		 * @param leagueID
		 * @param matcheDate
		 * @param winnerID
		 * @param loserID
		 * @param matchID
		 * @param longestRunForLoser 
		 * @param longestRunForWinner 
		 * @param score 
		 * @param length 
		 */
		public Match(final String leagueID, Date matcheDate, String winnerID, String loserID, final int matchID,
						int length, int score, int longestRunForWinner, int longestRunForLoser) {
			
			this.leagueID = leagueID;
			this.matchDate = matcheDate;
			this.winnerID = winnerID;
			this.loserID = loserID;
			this.matchID = matchID;
			this.length = length;
			this.score = score;
			this.longestRunForWinner = longestRunForWinner;
			this.longestRunForLoser = longestRunForLoser;
		}

		/**
		 * @return
		 */
		public int getLength() {
			return length;
		}

		public int getMatchID() {
			return matchID;
		}

		/**
		 * @param playerID
		 * @return
		 */
		public boolean isTheWinner(String playerID) {
			return getWinnerID().equals(playerID);
		}

		/**
		 * @param playerID
		 * @return
		 */
		public boolean isOnTheMatch(String playerID) {
			return (getWinnerID().equals(playerID) || getLoserID().equals(playerID));
		}

		/**
		 * @return the leagueID
		 */
		public String getLeagueID() {
			return leagueID;
		}

		/**
		 * @return the loserID
		 */
		public String getLoserID() {
			return loserID;
		}

		/**
		 * @return the matcheDate
		 */
		public Date getMatcheDate() {
			return matchDate;
		}

		/**
		 * @return the winnerID
		 */
		public String getWinnerID() {
			return winnerID;
		}

		/**
		 * @return the longestRunForLoser
		 */
		public int getLongestRunForLoser() {
			return longestRunForLoser;
		}

		/**
		 * @return the longestRunForWinner
		 */
		public int getLongestRunForWinner() {
			return longestRunForWinner;
		}

		/**
		 * @return the score
		 */
		public int getScore() {
			return score;
		}
		
		/**
		 * @param length the length to set
		 */
		public void setLength(int length) {
			this.length = length;
		}

		/**
		 * @param longestRunForLoser the longestRunForLoser to set
		 */
		public void setLongestRunForLoser(int longestRunForLoser) {
			this.longestRunForLoser = longestRunForLoser;
		}

		/**
		 * @param longestRunForWinner the longestRunForWinner to set
		 */
		public void setLongestRunForWinner(int longestRunForWinner) {
			this.longestRunForWinner = longestRunForWinner;
		}

		/**
		 * @param loserID the loserID to set
		 */
		public void setLoserID(String loserID) {
			this.loserID = loserID;
		}

		/**
		 * @param score the score to set
		 */
		public void setScore(int score) {
			this.score = score;
		}

		/**
		 * @param winnerID the winnerID to set
		 */
		public void setWinnerID(String winnerID) {
			this.winnerID = winnerID;
		}

		/**
		 * @param matchDate the matchDate to set
		 */
		public void setMatchDate(Date matchDate) {
			this.matchDate = matchDate;
		}

		/* (non-Javadoc)
		 * @see java.lang.Object#hashCode()
		 */
		@Override
		public int hashCode() {
			final int PRIME = 31;
			int result = 1;
			result = PRIME * result + matchID;
			return result;
		}

		/* (non-Javadoc)
		 * @see java.lang.Object#equals(java.lang.Object)
		 */
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			final Match other = (Match) obj;
			if (matchID != other.matchID)
				return false;
			return true;
		}
		
	}
	
}